ネットワーク分析とは?

例えば友人との交流や会社での人間関係、あるいは企業間の取引関係など、要素同士が何らかの関係で結びついた網の目のような構造をネットワークと呼びます。

このネットワークの構造や特徴を探るのがネットワーク分析です。

ネットワークの一例を示します。

これはアメリカの大学の空手クラブの人間関係を表したネットワークです。IrisやTaitanicのデータと同じように、ネットワーク分析のデモデータとしてよく使われるデータです。

library(conflicted)
library(tidyverse)
library(igraph)
library(tidygraph)
library(ggraph)
library(patchwork)
library(visNetwork)
library(igraphdata) # デモデータの用意

data("karate")
karate <- karate |>
  as_tbl_graph(directed = FALSE) 

set.seed(1)
karate |> 
  ggraph(layout = "fr") +
  geom_edge_link(
    aes(edge_width = weight),
    color = "gray50",
    alpha = 0.5
  ) +
  scale_edge_width(range = c(0.5, 3)) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 7) +
  geom_node_text(aes(label = label), color = "navy") +
  labs(title = "ネットワーク図") +
  theme_graph() +
  theme(aspect.ratio = 3/4)

ネットワークの分かりやすい例としてSNSのフォロー関係があります。

このネットワークを構成する円を「ノード」と呼びます。「頂点」とか「ヴァーテックス」と呼ばれることもあります。SNSで言えば個々のアカウントになります。

ノードとノードを結ぶ線を「エッジ」と呼びます。「辺」とか「リンク」と呼ばれることもあります。SNSで言えばフォロー関係などになります。エッジは方向性を持つ場合もあります。エッジが方向性を持つ場合のネットワークは「有向グラフ」、方向性を持たない場合は「無向グラフ」と呼ばれます。

ネットワークのデータはこの「ノード」と「エッジ」の2つの情報から構成されます。

ネットワーク分析のパッケージ

Rでネットワーク分析を行う場合、よく使われるのは以下のパッケージです。

  • igraph
    • ネットワーク分析の基本パッケージ。Python版もある
  • tidygraph
    • tidyにネットワーク分析を行うigraphのwrapper
  • ggraph
    • ggplot2ベースのネットワーク可視化
  • visNetwork
    • vis.jsベースの動的なネットワーク可視化

ネットワークデータの入力

まず、ネットワークのデータをRでどのように取り扱うか見てみましょう。

このようなシンプルなネットワークを考えます。

matrix(
  c(
    0,0,0,0,
    1,0,0,0,
    1,1,0,0,
    1,1,1,0
  ), nrow = 4, byrow = TRUE,
  dimnames = list(c("A", "B", "C", "D"),c("A", "B", "C", "D"))
) |>
  as_tbl_graph() |>
  ggraph(layout = "fr") +
  geom_edge_fan(color = "gray50", alpha = 0.5) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 7) +
  geom_node_text(aes(label = name), color = "navy") +
  labs(title = "シンプルなネットワーク") +
  theme_graph() +
  theme(aspect.ratio = 3/4)

このネットワークをデータとして扱うには「隣接行列」と「エッジリスト」の2つの方法があります。

隣接行列

隣接行列はマトリクス形式でノード間のエッジの存在と重みを表現します。

edges_matrix <- matrix(
  c(
    0,0,0,0,
    1,0,0,0,
    1,1,0,0,
    1,1,1,0
  ), nrow = 4, byrow = TRUE,
  dimnames = list(c("A", "B", "C", "D"),c("A", "B", "C", "D"))
)
edges_matrix
##   A B C D
## A 0 0 0 0
## B 1 0 0 0
## C 1 1 0 0
## D 1 1 1 0

エッジリスト

エッジリストはデータフレーム形式でform列とto列を持ち、各行にどのノードとどのノードにエッジが存在するかを表します。

edges_list <- data.frame(
  from = c("B", "C", "C", "D", "D", "D"),
  to   = c("A", "A", "B", "A", "B", "C")
)
edges_list
##   from to
## 1    B  A
## 2    C  A
## 3    C  B
## 4    D  A
## 5    D  B
## 6    D  C

どちらも場合もtidygraphパッケージのas_tbl_graph()関数を通してネットワークのオブジェクトに変換します。

# 隣接行列
g1 <- edges_matrix |>
  as_tbl_graph()
g1
## # A tbl_graph: 4 nodes and 6 edges
## #
## # A directed acyclic simple graph with 1 component
## #
## # Node Data: 4 × 1 (active)
##   name 
##   <chr>
## 1 A    
## 2 B    
## 3 C    
## 4 D    
## #
## # Edge Data: 6 × 3
##    from    to weight
##   <int> <int>  <dbl>
## 1     2     1      1
## 2     3     1      1
## 3     3     2      1
## # ℹ 3 more rows
# エッジリスト
g2 <- edges_list |>
  as_tbl_graph()
g2
## # A tbl_graph: 4 nodes and 6 edges
## #
## # A directed acyclic simple graph with 1 component
## #
## # Node Data: 4 × 1 (active)
##   name 
##   <chr>
## 1 B    
## 2 C    
## 3 D    
## 4 A    
## #
## # Edge Data: 6 × 2
##    from    to
##   <int> <int>
## 1     1     4
## 2     2     4
## 3     2     1
## # ℹ 3 more rows

エッジリストにはweight列がありませんが、これはエッジリストに追加すれば同じになります。

# エッジリスト
g2 <- edges_list |>
  mutate(weight = 1) |>
  as_tbl_graph()
g2
## # A tbl_graph: 4 nodes and 6 edges
## #
## # A directed acyclic simple graph with 1 component
## #
## # Node Data: 4 × 1 (active)
##   name 
##   <chr>
## 1 B    
## 2 C    
## 3 D    
## 4 A    
## #
## # Edge Data: 6 × 3
##    from    to weight
##   <int> <int>  <dbl>
## 1     1     4      1
## 2     2     4      1
## 3     2     1      1
## # ℹ 3 more rows

元のデータの表現は違いますが全く同じネットワークです。

set.seed(1)
p1 <- g1 |>
  as_tbl_graph() |>
  ggraph(layout = "fr") +
  geom_edge_fan(color = "gray50", alpha = 0.5) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 7) +
  geom_node_text(aes(label = name), color = "navy") +
  labs(title = "隣接行列") +
  theme_graph() +
  theme(aspect.ratio = 3/4)

set.seed(1)
p2 <- g2 |>
  as_tbl_graph() |>
  ggraph(layout = "fr") +
  geom_edge_fan(color = "gray50", alpha = 0.5) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 7) +
  geom_node_text(aes(label = name), color = "navy") +
  labs(title = "エッジリスト") +
  theme_graph() +
  theme(aspect.ratio = 3/4)

p1 + p2

ネットワーク図の描画

ネットワーク図の描画によく使われるのは以下の3つの方法です

  1. igraphパッケージのplot()関数
  2. ggraphパッケージのggraph()関数
  3. visNetworkパッケージのvisIgraph()関数

igraphパッケージのplot()関数

最もシンプルに描画できるのがigraphパッケージのplot()関数です。工夫すればキレイな描画も可能です。

set.seed(1)
karate |>
  plot(main = "igraph")

ggraphはggplot2ベースなのでggplot2に慣れている人には理解しやすいです。

karate |>
  ggraph(layout = "fr") +
  geom_edge_link(color = "gray50", alpha = 0.5) +
  geom_node_point(aes(fill = as.factor(color)), shape = 21, color = "black", size = 7) +
  geom_node_text(aes(label = label), color = "navy") +
  labs(title = "ggraph") +
  theme_graph() +
  theme(aspect.ratio = 3/4, legend.position = "none")

visNetworkパッケージを使うと動的なネットワーク図が描けます。

こちらは動的なネットワーク図を作れます。

ぐりぐり動かしてみましょう。

karate |>
  visIgraph()

ggraphパッケージでの描画

ここではggraphパッケージの使い方をもう少し詳しく紹介します。

ggraphパッケージはggplot2ベースでgeom_edge_*でエッジをgeom_node_*でノードを描くのが基本形になります。

karate |>
  ggraph() +
  geom_edge_link() + 
  geom_node_point()

これだとイマイチですね。ノードを大きくして色を付けてみましょう。ノードの設定はgeom_node_point()の引数でコントロールできます。

karate |> 
  ggraph() +
  geom_edge_link() +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 10)

エッジの色をグレーにしてみます。重なりが分かるように透明度も追加します。geom_edge_link()関数でコントロールできます。

karate |> 
  ggraph() +
  geom_edge_link(color = "gray", alpha = 0.5) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 10)

エッジの重みをエッジの太さに反映させてみます。ノードごとに値が変わる変数を指定する場合はggplot2同様にaes()関数で囲みます。

karate |> 
  ggraph() +
  geom_edge_link(aes(width = weight), color = "gray", alpha = 0.5) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 10)

ノードにラベルを貼ってみましょう。geom_node_text()を追加します。

karate |> 
  ggraph() +
  geom_edge_link(aes(width = weight), color = "gray") +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 10) +
  geom_node_text(aes(label = label), color = "navy")

最後の調整です。エッジの太さの範囲を指定して、背景を白にして、タイトルを付けます。

karate |> 
  ggraph() +
  geom_edge_link(aes(width = weight), color = "gray", alpha = 0.5) +
  scale_edge_width(range = c(0.5, 3)) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 10) +
  geom_node_text(aes(label = label), color = "navy") +
  labs(title = "ggraphによるネットワーク図") +
  theme_graph()

どうでしょうか。割といい感じでしょ?

様々なレイアウト

ggraphでは様々なレイアウト(ノードの配置)を指定できます。

例えば円形に並べるとこうなります。

set.seed(1)
karate |> 
  ggraph(layout = "circle") +
  geom_edge_link(
    aes(edge_width = weight),
    color = "gray50",
    alpha = 0.5
  ) +
  scale_edge_width(range = c(0.5, 3)) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 7) +
  geom_node_text(aes(label = label), color = "navy") +
  labs(title = 'layout = "circle"') +
  theme_graph() +
  theme(aspect.ratio = 1, legend.position = "none")

レイアウト例を並べてみます。

my_ggraph_layouts <- function(layout) {
  set.seed(2)
  karate |> 
    ggraph(layout = layout) +
    geom_edge_link(aes(width = weight), color = "gray") +
    scale_edge_width(range = c(0.5, 2)) +
    geom_node_point(shape = 21, color = "black", fill = "orange", size = 5) +
    labs(title = layout) +
    theme_graph() +
    theme(legend.position = "none", aspect.ratio = 1)
}

layouts <- c("sugiyama", "tree", "star", "circle", "dh",
             "gem", "graphopt", "grid", "mds", "sphere",
             "fr", "kk", "drl", "lgl")

layouts_plot <- map(layouts, \(x) my_ggraph_layouts(x))

{layouts_plot[[1]] | layouts_plot[[2]]} /
  {layouts_plot[[3]] | layouts_plot[[4]]} / 
  {layouts_plot[[5]] | layouts_plot[[6]]} /
  {layouts_plot[[7]] | layouts_plot[[8]]} /
  {layouts_plot[[9]] | layouts_plot[[10]]} / 
  {layouts_plot[[11]] | layouts_plot[[12]]} /
  {layouts_plot[[13]] | layouts_plot[[14]]}

エッジの表現もいろいろ選べます。

g3 <- matrix(
  c(0,1,1,1, 1,0,0,0, 1,1,0,0, 1,1,1,0), nrow = 4, byrow = TRUE,
  dimnames = list(c("A", "B", "C", "D"),c("A", "B", "C", "D"))
) |>
  as_tbl_graph()

set.seed(2)
p1 <- g3 |>
  ggraph("fr") +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_edge_link(arrow = arrow(),
                 end_cap = circle(3, "mm"),
                 start_cap = circle(3, "mm")) +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_link") +
  theme_graph()

set.seed(2)
p2 <- g3 |>
  ggraph("fr") +
  geom_edge_arc(arrow = arrow(),
                end_cap = circle(3, "mm"),
                start_cap = circle(3, "mm")) +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_arc") +
  theme_graph()

set.seed(2)
p3 <- g3 |>
  ggraph("fr") +
  geom_edge_bend(arrow = arrow(),
                 end_cap = circle(3, "mm"),
                 start_cap = circle(3, "mm")) +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_bend") +
  theme_graph()

set.seed(2)
p4 <- g3 |>
  ggraph("fr") +
  geom_edge_hive(arrow = arrow(),
                 end_cap = circle(3, "mm"),
                 start_cap = circle(3, "mm")) +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_hive") +
  theme_graph()

# 複線対応
set.seed(2)
p5 <- g3 |>
  ggraph("fr") +
  geom_edge_fan(arrow = arrow(),
                end_cap = circle(3, "mm"),
                start_cap = circle(3, "mm")) +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_fan") +
  theme_graph()

set.seed(2)
p6 <- g3 |>
  ggraph("fr") +
  geom_edge_parallel(arrow = arrow(),
                     end_cap = circle(3, "mm"),
                     start_cap = circle(3, "mm")) +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_parallel") +
  theme_graph()

# 自己ループ追加
set.seed(2)
p7 <- data.frame(
  from = c("A", "A", "A", "B", "C", "C", "D", "D", "D", "B", "C", "D"),
  to   = c("B", "C", "D", "A", "A", "B", "A", "B", "C", "B", "C", "D")
  ) |>
  as_tbl_graph() |>
  ggraph("fr") +
  geom_edge_fan(arrow = arrow(),
                end_cap = circle(3, "mm"),
                start_cap = circle(3, "mm")) +
  geom_edge_loop(arrow = arrow(),
                 end_cap = circle(3, "mm"),
                 start_cap = circle(3, "mm")) +
  geom_node_point(size=6, shape = 21, color = "black", fill = "orange") +
  geom_node_text(aes(label = name)) +
  labs(title = "geom_edge_loop") +
  theme_graph()

{p1 | p2 } /
  {p3 | p4 } /
  {p5 | p6} /
  {p7 | plot_spacer()}

ネットワークの中心性指標

さて、そろそろ分析っぽいことをしましょうか。

ネットワーク構造から各ノードがどれくらい中心的な役割を果たしているのかを定量化するのが「中心性指標」です。

SNSで言えばフォロワーが多いほど重要人物っぽいと考えられますよね。そのような考え方です。

中心性指標にはいくつかの手法が提案されています。ここでは代表的なものを3つ挙げます。

  • 次数中心性:接続しているエッジの数。有向グラフの場合は他のノードから入ってくる入次数、他のノードへ出て行く出次数が区別されます。
  • ページランク:次数中心性はすべてのエッジを等しく扱いますが、接続先のノードの中心性に応じてエッジに重みづけをするのがページランクです。
  • 媒介中心性:次数ではなく、経路に注目するのが媒介中心性です。すべてのノード間の経路を考えたときに、ノードごとに通過する頻度を数えたのが媒介中心性です。

実際に計算してみましょう。

ネットワークのオブジェクト(tbl_graphクラス)にはノードとエッジの2つの情報が格納されているので、まずactivate()関数でノードの方を指定してから、通常のデータフレームと同じようにmutate()で各ノードの中心性指標を追加します。

gc <- g3 |>
  activate(nodes) |>
  mutate(
    cd_in = centrality_degree(weights = weight, mode = "in"),
    cd_out = centrality_degree(weights = weight, mode = "out"),
    cd_all = centrality_degree(weights = weight, mode = "all"),
    between = centrality_betweenness(weights = weight),
    pagerank = centrality_pagerank(weights = weight)
  )

# 描画
set.seed(1)
p1 <- gc |>
  ggraph("fr", weights = weight) +
  geom_edge_fan(
    aes(width = weight),
    color = "gray40", alpha = 0.5,arrow = arrow(), end_cap = circle(6, "mm"), start_cap = circle(6, "mm")) +
  scale_edge_width(range = c(1,5)) +
  geom_node_point(aes(size=cd_in), shape = 21, color = "black", fill = "orange") +
  scale_size(range = c(3,12)) +
  geom_node_text(aes(label = cd_in)) +
  labs(title = "次数中心性(in)") +
  theme_graph() +
  theme(legend.position = "none", aspect.ratio = 1)
set.seed(1)
p2 <- gc |>
  ggraph("fr", weights = weight) +
  geom_edge_fan(
    aes(width = weight),
    color = "gray40", alpha = 0.5,arrow = arrow(), end_cap = circle(6, "mm"), start_cap = circle(6, "mm")) +
  scale_edge_width(range = c(1,5)) +
  geom_node_point(aes(size=cd_out), shape = 21, color = "black", fill = "orange") +
  scale_size(range = c(3,15)) +
  geom_node_text(aes(label = cd_out)) +
  labs(title = "次数中心性(out)") +
  theme_graph() +
  theme(legend.position = "none", aspect.ratio = 1)
set.seed(1)
p3 <- gc |>
  ggraph("fr", weights = weight) +
  geom_edge_fan(
    aes(width = weight),
    color = "gray40", alpha = 0.5,arrow = arrow(), end_cap = circle(6, "mm"), start_cap = circle(6, "mm")) +
  scale_edge_width(range = c(1,5)) +
  geom_node_point(aes(size=cd_all), shape = 21, color = "black", fill = "orange") +
  scale_size(range = c(3,15)) +
  geom_node_text(aes(label = cd_all)) +
  labs(title = "次数中心性(all)") +
  theme_graph() +
  theme(legend.position = "none", aspect.ratio = 1)
set.seed(1)
p4 <- gc |>
  ggraph("fr", weights = weight) +
  geom_edge_fan(
    aes(width = weight),
    color = "gray40", alpha = 0.5,arrow = arrow(), end_cap = circle(6, "mm"), start_cap = circle(6, "mm")) +
  scale_edge_width(range = c(1,5)) +
  geom_node_point(aes(size=between), shape = 21, color = "black", fill = "orange") +
  scale_size(range = c(3,15)) +
  geom_node_text(aes(label = between)) +
  labs(title = "媒介中心性") +
  theme_graph() +
  theme(legend.position = "none", aspect.ratio = 1)
set.seed(1)
p5 <- gc |>
  ggraph("fr", weights = weight) +
  geom_edge_fan(
    aes(width = weight),
    color = "gray40", alpha = 0.5,arrow = arrow(), end_cap = circle(6, "mm"), start_cap = circle(6, "mm")) +
  scale_edge_width(range = c(1,5)) +
  geom_node_point(aes(size=pagerank), shape = 21, color = "black", fill = "orange") +
  scale_size(range = c(3,15)) +
  geom_node_text(aes(label = round(pagerank,2))) +
  labs(title = "ページランク") +
  theme_graph() +
  theme(legend.position = "none", aspect.ratio = 1)

p1 + p2 + p3 + p4 + p5 + plot_spacer() + plot_layout(ncol = 2)

数値も確認しましょう。

こちらもactivate()でノードを指定してからデータフレームに変換すればOKです。

gc |>
  activate(nodes) |>
  as_tibble()
## # A tibble: 4 × 6
##   name  cd_in cd_out cd_all between pagerank
##   <chr> <dbl>  <dbl>  <dbl>   <dbl>    <dbl>
## 1 A         3      3      6       3    0.391
## 2 B         3      1      4       0    0.271
## 3 C         2      2      4       0    0.190
## 4 D         1      3      4       0    0.148

コミュニティ抽出

次にコミュニティ抽出を行います。これはクラスター分析のネットワーク版です。

まずは分かりやすいネットワークで試してみましょう。

明らかに4つのグループが存在します。

islands <- play_islands(
  n_islands = 4, 
  size_islands = 15,
  p_within = 0.7,
  m_between = 3
  )

islands |>
  ggraph(layout = "fr") +
  geom_edge_fan(color = "gray", alpha = 0.7) +
  geom_node_point(shape = 21, color = "black", fill = "orange", size = 6) +
  theme_graph()

このグラフをコミュニティ抽出します。

これもいくつかの手法が提案されており、今回は以下の4つの手法を試してみます。

  • 最適化貪欲アルゴリズム
  • ルーヴェン法
  • インフォマップ法
  • エッジの媒介中心性に基づくコミュニティ抽出
islands_clust <- islands |>
  activate(nodes) |>
  mutate(
    fast_greedy = as.factor(group_fast_greedy()),
    louvain = as.factor(group_louvain()),
    infomap = as.factor(group_infomap()),
    edge_betweenness = as.factor(group_edge_betweenness())
  )

ネットワーク図で確認しましょう。どの手法でも適切にグループ分けできているようです。

set.seed(1)
p1 <- islands_clust |>
  ggraph("fr") +
  geom_edge_link(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = fast_greedy), size = 6, shape = 21, color = "black") +
  labs(title = "Greedy optimization of modularity") +
  theme_graph() +
  theme(legend.position = "none")

set.seed(1)
p2 <- islands_clust  |>
  ggraph("fr") +
  geom_edge_fan(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = louvain), size = 6, shape = 21, color = "black") +
  labs(title = "The Louvain algorithm") +
  theme_graph() +
  theme(legend.position = "none")

set.seed(1)
p3 <- islands_clust  |>
  ggraph("fr") +
  geom_edge_fan(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = infomap), size = 6, shape = 21, color = "black") +
  labs(title = "The Infomap algorithm") +
  theme_graph() +
  theme(legend.position = "none")

set.seed(1)
p4 <- islands_clust  |>
  ggraph("fr") +
  geom_edge_fan(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = edge_betweenness), size = 6, shape = 21, color = "black") +
  labs(title = "Community structure detection\n based on edge betweenness") +
  theme_graph() +
  theme(legend.position = "none")

{p1|p2}/{p3|p4}

なお、最適化貪欲アルゴリズムとエッジの媒介中心性に基づくコミュニティ抽出は階層型の手法なので、デンドログラムを描くこともできます。

islands |>
  cluster_fast_greedy() |>
  plot_dendrogram(main = "最適化貪欲アルゴリズム")

空手クラブのデータでも試してみましょう。

karate_clust <- karate |>
  activate(nodes) |>
  mutate(
    fast_greedy = as.factor(group_fast_greedy()),
    louvain = as.factor(group_louvain()),
    infomap = as.factor(group_infomap()),
    edge_betweenness = as.factor(group_edge_betweenness())
  )
set.seed(1)
p1 <- karate_clust |>
  ggraph("fr") +
  geom_edge_link(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = fast_greedy), size = 6, shape = 21, color = "black") +
  labs(title = "Greedy optimization of modularity") +
  theme_graph()
set.seed(1)
p2 <- karate_clust  |>
  ggraph("fr") +
  geom_edge_fan(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = louvain), size = 6, shape = 21, color = "black") +
  labs(title = "The Louvain algorithm") +
  theme_graph()
set.seed(1)
p3 <- karate_clust  |>
  ggraph("fr") +
  geom_edge_fan(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = infomap), size = 6, shape = 21, color = "black") +
  labs(title = "The Infomap algorithm") +
  theme_graph()
set.seed(1)
p4 <- karate_clust  |>
  ggraph("fr") +
  geom_edge_fan(color = "gray40", alpha = 0.5) +
  geom_node_point(aes(fill = edge_betweenness), size = 6, shape = 21, color = "black") +
  labs(title = "Community structure detection\n based on edge betweenness") +
  theme_graph()

{p1|p2}/{p3|p4}

LS0tCnRpdGxlOiAi44ON44OD44OI44Ov44O844Kv5YiG5p6Q44Gu5Z+656SOIgphdXRob3I6ICJib2IzYm9iMyIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0aGVtZTogdW5pdGVkICAgIAogICAgbWRfZXh0ZW5zaW9uczogIi1hc2NpaV9pZGVudGlmaWVycyIKICAgIHRvY19mbG9hdDogeWVzCiAgICBmaWdfd2lkdGg6IDcuNQogICAgZmlnX2hlaWdodDogNS42MjUKICAgIGRldjogcmFnZ19wbmcKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGNvZGVfZm9sZGluZzogc2hvdwogIyAgIGRmX3ByaW50OiBwYWdlZAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyMg44ON44OD44OI44Ov44O844Kv5YiG5p6Q44Go44Gv77yfCgrkvovjgYjjgbDlj4vkurrjgajjga7kuqTmtYHjgoTkvJrnpL7jgafjga7kurrplpPplqLkv4LjgIHjgYLjgovjgYTjga/kvIHmpa3plpPjga7lj5blvJXplqLkv4LjgarjganjgIHopoHntKDlkIzlo6vjgYzkvZXjgonjgYvjga7plqLkv4LjgafntZDjgbPjgaTjgYTjgZ/ntrLjga7nm67jga7jgojjgYbjgarmp4vpgKDjgpLjg43jg4Pjg4jjg6/jg7zjgq/jgajlkbzjgbPjgb7jgZnjgIIKCuOBk+OBruODjeODg+ODiOODr+ODvOOCr+OBruani+mAoOOChOeJueW+tOOCkuaOouOCi+OBruOBjOODjeODg+ODiOODr+ODvOOCr+WIhuaekOOBp+OBmeOAggoK44ON44OD44OI44Ov44O844Kv44Gu5LiA5L6L44KS56S644GX44G+44GZ44CCCgrjgZPjgozjga/jgqLjg6Hjg6rjgqvjga7lpKflrabjga7nqbrmiYvjgq/jg6njg5bjga7kurrplpPplqLkv4LjgpLooajjgZfjgZ/jg43jg4Pjg4jjg6/jg7zjgq/jgafjgZnjgIJJcmlz44KEVGFpdGFuaWPjga7jg4fjg7zjgr/jgajlkIzjgZjjgojjgYbjgavjgIHjg43jg4Pjg4jjg6/jg7zjgq/liIbmnpDjga7jg4fjg6Ljg4fjg7zjgr/jgajjgZfjgabjgojjgY/kvb/jgo/jgozjgovjg4fjg7zjgr/jgafjgZnjgIIKCmBgYHtyIGxpYnJhcnksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KHZpc05ldHdvcmspCmxpYnJhcnkoaWdyYXBoZGF0YSkgIyDjg4fjg6Ljg4fjg7zjgr/jga7nlKjmhI8KCmRhdGEoImthcmF0ZSIpCmthcmF0ZSA8LSBrYXJhdGUgfD4KICBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGQUxTRSkgCgpzZXQuc2VlZCgxKQprYXJhdGUgfD4gCiAgZ2dyYXBoKGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluaygKICAgIGFlcyhlZGdlX3dpZHRoID0gd2VpZ2h0KSwKICAgIGNvbG9yID0gImdyYXk1MCIsCiAgICBhbHBoYSA9IDAuNQogICkgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAuNSwgMykpICsKICBnZW9tX25vZGVfcG9pbnQoc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIsIHNpemUgPSA3KSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbGFiZWwpLCBjb2xvciA9ICJuYXZ5IikgKwogIGxhYnModGl0bGUgPSAi44ON44OD44OI44Ov44O844Kv5ZuzIikgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDMvNCkKYGBgCgrjg43jg4Pjg4jjg6/jg7zjgq/jga7liIbjgYvjgorjgoTjgZnjgYTkvovjgajjgZfjgaZTTlPjga7jg5Xjgqnjg63jg7zplqLkv4LjgYzjgYLjgorjgb7jgZnjgIIKCuOBk+OBruODjeODg+ODiOODr+ODvOOCr+OCkuani+aIkOOBmeOCi+WGhuOCkuOAjOODjuODvOODieOAjeOBqOWRvOOBs+OBvuOBmeOAguOAjOmggueCueOAjeOBqOOBi+OAjOODtOOCoeODvOODhuODg+OCr+OCueOAjeOBqOWRvOOBsOOCjOOCi+OBk+OBqOOCguOBguOCiuOBvuOBmeOAglNOU+OBp+iogOOBiOOBsOWAi+OAheOBruOCouOCq+OCpuODs+ODiOOBq+OBquOCiuOBvuOBmeOAggoK44OO44O844OJ44Go44OO44O844OJ44KS57WQ44G257ea44KS44CM44Ko44OD44K444CN44Go5ZG844Gz44G+44GZ44CC44CM6L6644CN44Go44GL44CM44Oq44Oz44Kv44CN44Go5ZG844Gw44KM44KL44GT44Go44KC44GC44KK44G+44GZ44CCU05T44Gn6KiA44GI44Gw44OV44Kp44Ot44O86Zai5L+C44Gq44Gp44Gr44Gq44KK44G+44GZ44CC44Ko44OD44K444Gv5pa55ZCR5oCn44KS5oyB44Gk5aC05ZCI44KC44GC44KK44G+44GZ44CC44Ko44OD44K444GM5pa55ZCR5oCn44KS5oyB44Gk5aC05ZCI44Gu44ON44OD44OI44Ov44O844Kv44Gv44CM5pyJ5ZCR44Kw44Op44OV44CN44CB5pa55ZCR5oCn44KS5oyB44Gf44Gq44GE5aC05ZCI44Gv44CM54Sh5ZCR44Kw44Op44OV44CN44Go5ZG844Gw44KM44G+44GZ44CCCgrjg43jg4Pjg4jjg6/jg7zjgq/jga7jg4fjg7zjgr/jga/jgZPjga7jgIzjg47jg7zjg4njgI3jgajjgIzjgqjjg4PjgrjjgI3jga4y44Gk44Gu5oOF5aCx44GL44KJ5qeL5oiQ44GV44KM44G+44GZ44CCCgojIyDjg43jg4Pjg4jjg6/jg7zjgq/liIbmnpDjga7jg5Hjg4PjgrHjg7zjgrgKClLjgafjg43jg4Pjg4jjg6/jg7zjgq/liIbmnpDjgpLooYzjgYbloLTlkIjjgIHjgojjgY/kvb/jgo/jgozjgovjga7jga/ku6XkuIvjga7jg5Hjg4PjgrHjg7zjgrjjgafjgZnjgIIKCiAgIC0gW2lncmFwaF0oaHR0cHM6Ly9yLmlncmFwaC5vcmcvKQogICAgICAtIOODjeODg+ODiOODr+ODvOOCr+WIhuaekOOBruWfuuacrOODkeODg+OCseODvOOCuOOAglB5dGhvbueJiOOCguOBguOCiyAKICAgLSBbdGlkeWdyYXBoXShodHRwczovL3RpZHlncmFwaC5kYXRhLWltYWdpbmlzdC5jb20vKQogICAgICAtIHRpZHnjgavjg43jg4Pjg4jjg6/jg7zjgq/liIbmnpDjgpLooYzjgYZpZ3JhcGjjga53cmFwcGVyIAogICAtIFtnZ3JhcGhdKGh0dHBzOi8vZ2dyYXBoLmRhdGEtaW1hZ2luaXN0LmNvbS8pCiAgICAgIC0gZ2dwbG90MuODmeODvOOCueOBruODjeODg+ODiOODr+ODvOOCr+WPr+imluWMlgogICAtIFt2aXNOZXR3b3JrXShodHRwczovL2RhdGFzdG9ybS1vcGVuLmdpdGh1Yi5pby92aXNOZXR3b3JrLykKICAgICAgLSB2aXMuanPjg5njg7zjgrnjga7li5XnmoTjgarjg43jg4Pjg4jjg6/jg7zjgq/lj6/oppbljJYgCgoKIyMg44ON44OD44OI44Ov44O844Kv44OH44O844K/44Gu5YWl5YqbCgrjgb7jgZrjgIHjg43jg4Pjg4jjg6/jg7zjgq/jga7jg4fjg7zjgr/jgpJS44Gn44Gp44Gu44KI44GG44Gr5Y+W44KK5omx44GG44GL6KaL44Gm44G/44G+44GX44KH44GG44CCCgrjgZPjga7jgojjgYbjgarjgrfjg7Pjg5fjg6vjgarjg43jg4Pjg4jjg6/jg7zjgq/jgpLogIPjgYjjgb7jgZnjgIIKCmBgYHtyIGlucHV0fQptYXRyaXgoCiAgYygKICAgIDAsMCwwLDAsCiAgICAxLDAsMCwwLAogICAgMSwxLDAsMCwKICAgIDEsMSwxLDAKICApLCBucm93ID0gNCwgYnlyb3cgPSBUUlVFLAogIGRpbW5hbWVzID0gbGlzdChjKCJBIiwgIkIiLCAiQyIsICJEIiksYygiQSIsICJCIiwgIkMiLCAiRCIpKQopIHw+CiAgYXNfdGJsX2dyYXBoKCkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9mYW4oY29sb3IgPSAiZ3JheTUwIiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIsIHNpemUgPSA3KSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIGNvbG9yID0gIm5hdnkiKSArCiAgbGFicyh0aXRsZSA9ICLjgrfjg7Pjg5fjg6vjgarjg43jg4Pjg4jjg6/jg7zjgq8iKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMy80KQpgYGAKCuOBk+OBruODjeODg+ODiOODr+ODvOOCr+OCkuODh+ODvOOCv+OBqOOBl+OBpuaJseOBhuOBq+OBr+OAjOmao+aOpeihjOWIl+OAjeOBqOOAjOOCqOODg+OCuOODquOCueODiOOAjeOBrjLjgaTjga7mlrnms5XjgYzjgYLjgorjgb7jgZnjgIIKCiMjIyDpmqPmjqXooYzliJcKCumao+aOpeihjOWIl+OBr+ODnuODiOODquOCr+OCueW9ouW8j+OBp+ODjuODvOODiemWk+OBruOCqOODg+OCuOOBruWtmOWcqOOBqOmHjeOBv+OCkuihqOePvuOBl+OBvuOBmeOAggoKYGBge3J9CmVkZ2VzX21hdHJpeCA8LSBtYXRyaXgoCiAgYygKICAgIDAsMCwwLDAsCiAgICAxLDAsMCwwLAogICAgMSwxLDAsMCwKICAgIDEsMSwxLDAKICApLCBucm93ID0gNCwgYnlyb3cgPSBUUlVFLAogIGRpbW5hbWVzID0gbGlzdChjKCJBIiwgIkIiLCAiQyIsICJEIiksYygiQSIsICJCIiwgIkMiLCAiRCIpKQopCmVkZ2VzX21hdHJpeApgYGAKCiMjIyDjgqjjg4Pjgrjjg6rjgrnjg4gKCuOCqOODg+OCuOODquOCueODiOOBr+ODh+ODvOOCv+ODleODrOODvOODoOW9ouW8j+OBp2Zvcm3liJfjgah0b+WIl+OCkuaMgeOBoeOAgeWQhOihjOOBq+OBqeOBruODjuODvOODieOBqOOBqeOBruODjuODvOODieOBq+OCqOODg+OCuOOBjOWtmOWcqOOBmeOCi+OBi+OCkuihqOOBl+OBvuOBmeOAggoKYGBge3J9CmVkZ2VzX2xpc3QgPC0gZGF0YS5mcmFtZSgKICBmcm9tID0gYygiQiIsICJDIiwgIkMiLCAiRCIsICJEIiwgIkQiKSwKICB0byAgID0gYygiQSIsICJBIiwgIkIiLCAiQSIsICJCIiwgIkMiKQopCmVkZ2VzX2xpc3QKYGBgCgrjganjgaHjgonjgoLloLTlkIjjgoJ0aWR5Z3JhcGjjg5Hjg4PjgrHjg7zjgrjjga5hc190YmxfZ3JhcGgoKemWouaVsOOCkumAmuOBl+OBpuODjeODg+ODiOODr+ODvOOCr+OBruOCquODluOCuOOCp+OCr+ODiOOBq+WkieaPm+OBl+OBvuOBmeOAggoKYGBge3J9CiMg6Zqj5o6l6KGM5YiXCmcxIDwtIGVkZ2VzX21hdHJpeCB8PgogIGFzX3RibF9ncmFwaCgpCmcxCmBgYAoKICAKYGBge3J9CiMg44Ko44OD44K444Oq44K544OICmcyIDwtIGVkZ2VzX2xpc3QgfD4KICBhc190YmxfZ3JhcGgoKQpnMgpgYGAKCuOCqOODg+OCuOODquOCueODiOOBq+OBr3dlaWdodOWIl+OBjOOBguOCiuOBvuOBm+OCk+OBjOOAgeOBk+OCjOOBr+OCqOODg+OCuOODquOCueODiOOBq+i/veWKoOOBmeOCjOOBsOWQjOOBmOOBq+OBquOCiuOBvuOBmeOAggoKYGBge3J9CiMg44Ko44OD44K444Oq44K544OICmcyIDwtIGVkZ2VzX2xpc3QgfD4KICBtdXRhdGUod2VpZ2h0ID0gMSkgfD4KICBhc190YmxfZ3JhcGgoKQpnMgpgYGAKCuWFg+OBruODh+ODvOOCv+OBruihqOePvuOBr+mBleOBhOOBvuOBmeOBjOWFqOOBj+WQjOOBmOODjeODg+ODiOODr+ODvOOCr+OBp+OBmeOAggpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0Kc2V0LnNlZWQoMSkKcDEgPC0gZzEgfD4KICBhc190YmxfZ3JhcGgoKSB8PgogIGdncmFwaChsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2Zhbihjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAib3JhbmdlIiwgc2l6ZSA9IDcpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwgY29sb3IgPSAibmF2eSIpICsKICBsYWJzKHRpdGxlID0gIumao+aOpeihjOWIlyIpICsKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAzLzQpCgpzZXQuc2VlZCgxKQpwMiA8LSBnMiB8PgogIGFzX3RibF9ncmFwaCgpIHw+CiAgZ2dyYXBoKGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfZmFuKGNvbG9yID0gImdyYXk1MCIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiLCBzaXplID0gNykgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCBjb2xvciA9ICJuYXZ5IikgKwogIGxhYnModGl0bGUgPSAi44Ko44OD44K444Oq44K544OIIikgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDMvNCkKCnAxICsgcDIKYGBgCgojIyDjg43jg4Pjg4jjg6/jg7zjgq/lm7Pjga7mj4/nlLsKCuODjeODg+ODiOODr+ODvOOCr+Wbs+OBruaPj+eUu+OBq+OCiOOBj+S9v+OCj+OCjOOCi+OBruOBr+S7peS4i+OBrjPjgaTjga7mlrnms5XjgafjgZkKCjEuIGlncmFwaOODkeODg+OCseODvOOCuOOBrnBsb3QoKemWouaVsAoyLiBnZ3JhcGjjg5Hjg4PjgrHjg7zjgrjjga5nZ3JhcGgoKemWouaVsAozLiB2aXNOZXR3b3Jr44OR44OD44Kx44O844K444GudmlzSWdyYXBoKCnplqLmlbAKCgojIyMgaWdyYXBo44OR44OD44Kx44O844K444GucGxvdCgp6Zai5pWwCgrmnIDjgoLjgrfjg7Pjg5fjg6vjgavmj4/nlLvjgafjgY3jgovjga7jgYxpZ3JhcGjjg5Hjg4PjgrHjg7zjgrjjga5wbG90KCnplqLmlbDjgafjgZnjgILlt6XlpKvjgZnjgozjgbDjgq3jg6zjgqTjgarmj4/nlLvjgoLlj6/og73jgafjgZnjgIIKYGBge3J9CnNldC5zZWVkKDEpCmthcmF0ZSB8PgogIHBsb3QobWFpbiA9ICJpZ3JhcGgiKQpgYGAKCmdncmFwaOOBr2dncGxvdDLjg5njg7zjgrnjgarjga7jgadnZ3Bsb3Qy44Gr5oWj44KM44Gm44GE44KL5Lq644Gr44Gv55CG6Kej44GX44KE44GZ44GE44Gn44GZ44CCCmBgYHtyfQprYXJhdGUgfD4KICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gImdyYXk1MCIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhmaWxsID0gYXMuZmFjdG9yKGNvbG9yKSksIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDcpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBsYWJlbCksIGNvbG9yID0gIm5hdnkiKSArCiAgbGFicyh0aXRsZSA9ICJnZ3JhcGgiKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMy80LCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKdmlzTmV0d29ya+ODkeODg+OCseODvOOCuOOCkuS9v+OBhuOBqOWLleeahOOBquODjeODg+ODiOODr+ODvOOCr+Wbs+OBjOaPj+OBkeOBvuOBmeOAggoK44GT44Gh44KJ44Gv5YuV55qE44Gq44ON44OD44OI44Ov44O844Kv5Zuz44KS5L2c44KM44G+44GZ44CCCgrjgZDjgorjgZDjgorli5XjgYvjgZfjgabjgb/jgb7jgZfjgofjgYbjgIIKYGBge3J9CmthcmF0ZSB8PgogIHZpc0lncmFwaCgpCmBgYAoKCiMjIGdncmFwaOODkeODg+OCseODvOOCuOOBp+OBruaPj+eUuwoK44GT44GT44Gn44GvZ2dyYXBo44OR44OD44Kx44O844K444Gu5L2/44GE5pa544KS44KC44GG5bCR44GX6Kmz44GX44GP57S55LuL44GX44G+44GZ44CCCgpnZ3JhcGjjg5Hjg4PjgrHjg7zjgrjjga9nZ3Bsb3Qy44OZ44O844K544GnYGdlb21fZWRnZV8qYOOBp+OCqOODg+OCuOOCkmBnZW9tX25vZGVfKmDjgafjg47jg7zjg4njgpLmj4/jgY/jga7jgYzln7rmnKzlvaLjgavjgarjgorjgb7jgZnjgIIKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmthcmF0ZSB8PgogIGdncmFwaCgpICsKICBnZW9tX2VkZ2VfbGluaygpICsgCiAgZ2VvbV9ub2RlX3BvaW50KCkKYGBgCgrjgZPjgozjgaDjgajjgqTjg57jgqTjg4HjgafjgZnjga3jgILjg47jg7zjg4njgpLlpKfjgY3jgY/jgZfjgaboibLjgpLku5jjgZHjgabjgb/jgb7jgZfjgofjgYbjgILjg47jg7zjg4njga7oqK3lrprjga9gZ2VvbV9ub2RlX3BvaW50KClg44Gu5byV5pWw44Gn44Kz44Oz44OI44Ot44O844Or44Gn44GN44G+44GZ44CCCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmthcmF0ZSB8PiAKICBnZ3JhcGgoKSArCiAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiLCBzaXplID0gMTApCmBgYAoKCgrjgqjjg4Pjgrjjga7oibLjgpLjgrDjg6zjg7zjgavjgZfjgabjgb/jgb7jgZnjgILph43jgarjgorjgYzliIbjgYvjgovjgojjgYbjgavpgI/mmI7luqbjgoLov73liqDjgZfjgb7jgZnjgIJgZ2VvbV9lZGdlX2xpbmsoKWDplqLmlbDjgafjgrPjg7Pjg4jjg63jg7zjg6vjgafjgY3jgb7jgZnjgIIKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka2FyYXRlIHw+IAogIGdncmFwaCgpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICJncmF5IiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIsIHNpemUgPSAxMCkKYGBgCgrjgqjjg4Pjgrjjga7ph43jgb/jgpLjgqjjg4Pjgrjjga7lpKrjgZXjgavlj43mmKDjgZXjgZvjgabjgb/jgb7jgZnjgILjg47jg7zjg4njgZTjgajjgavlgKTjgYzlpInjgo/jgovlpInmlbDjgpLmjIflrprjgZnjgovloLTlkIjjga9nZ3Bsb3Qy5ZCM5qeY44GrYWVzKCnplqLmlbDjgaflm7Ljgb/jgb7jgZnjgIIKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka2FyYXRlIHw+IAogIGdncmFwaCgpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQpLCBjb2xvciA9ICJncmF5IiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIsIHNpemUgPSAxMCkKYGBgCgrjg47jg7zjg4njgavjg6njg5njg6vjgpLosrzjgaPjgabjgb/jgb7jgZfjgofjgYbjgIJgZ2VvbV9ub2RlX3RleHQoKWDjgpLov73liqDjgZfjgb7jgZnjgIIKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka2FyYXRlIHw+IAogIGdncmFwaCgpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQpLCBjb2xvciA9ICJncmF5IikgKwogIGdlb21fbm9kZV9wb2ludChzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAib3JhbmdlIiwgc2l6ZSA9IDEwKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbGFiZWwpLCBjb2xvciA9ICJuYXZ5IikKYGBgCgrmnIDlvozjga7oqr/mlbTjgafjgZnjgILjgqjjg4Pjgrjjga7lpKrjgZXjga7nr4Tlm7LjgpLmjIflrprjgZfjgabjgIHog4zmma/jgpLnmb3jgavjgZfjgabjgIHjgr/jgqTjg4jjg6vjgpLku5jjgZHjgb7jgZnjgIIKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka2FyYXRlIHw+IAogIGdncmFwaCgpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQpLCBjb2xvciA9ICJncmF5IiwgYWxwaGEgPSAwLjUpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLjUsIDMpKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiLCBzaXplID0gMTApICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBsYWJlbCksIGNvbG9yID0gIm5hdnkiKSArCiAgbGFicyh0aXRsZSA9ICJnZ3JhcGjjgavjgojjgovjg43jg4Pjg4jjg6/jg7zjgq/lm7MiKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKCuOBqeOBhuOBp+OBl+OCh+OBhuOBi+OAguWJsuOBqOOBhOOBhOaEn+OBmOOBp+OBl+OCh++8nwoKCiMjIyDmp5jjgIXjgarjg6zjgqTjgqLjgqbjg4gKCmdncmFwaOOBp+OBr+anmOOAheOBquODrOOCpOOCouOCpuODiO+8iOODjuODvOODieOBrumFjee9ru+8ieOCkuaMh+WumuOBp+OBjeOBvuOBmeOAggoK5L6L44GI44Gw5YaG5b2i44Gr5Lim44G544KL44Go44GT44GG44Gq44KK44G+44GZ44CCCgpgYGB7cn0Kc2V0LnNlZWQoMSkKa2FyYXRlIHw+IAogIGdncmFwaChsYXlvdXQgPSAiY2lyY2xlIikgKwogIGdlb21fZWRnZV9saW5rKAogICAgYWVzKGVkZ2Vfd2lkdGggPSB3ZWlnaHQpLAogICAgY29sb3IgPSAiZ3JheTUwIiwKICAgIGFscGhhID0gMC41CiAgKSArCiAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMC41LCAzKSkgKwogIGdlb21fbm9kZV9wb2ludChzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAib3JhbmdlIiwgc2l6ZSA9IDcpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBsYWJlbCksIGNvbG9yID0gIm5hdnkiKSArCiAgbGFicyh0aXRsZSA9ICdsYXlvdXQgPSAiY2lyY2xlIicpICsKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoK44Os44Kk44Ki44Km44OI5L6L44KS5Lim44G544Gm44G/44G+44GZ44CCCgpgYGB7ciBmaWcuaGVpZ2h0PTM1LCBmaWcud2lkdGg9MTB9Cm15X2dncmFwaF9sYXlvdXRzIDwtIGZ1bmN0aW9uKGxheW91dCkgewogIHNldC5zZWVkKDIpCiAga2FyYXRlIHw+IAogICAgZ2dyYXBoKGxheW91dCA9IGxheW91dCkgKwogICAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gd2VpZ2h0KSwgY29sb3IgPSAiZ3JheSIpICsKICAgIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAuNSwgMikpICsKICAgIGdlb21fbm9kZV9wb2ludChzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAib3JhbmdlIiwgc2l6ZSA9IDUpICsKICAgIGxhYnModGl0bGUgPSBsYXlvdXQpICsKICAgIHRoZW1lX2dyYXBoKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBhc3BlY3QucmF0aW8gPSAxKQp9CgpsYXlvdXRzIDwtIGMoInN1Z2l5YW1hIiwgInRyZWUiLCAic3RhciIsICJjaXJjbGUiLCAiZGgiLAogICAgICAgICAgICAgImdlbSIsICJncmFwaG9wdCIsICJncmlkIiwgIm1kcyIsICJzcGhlcmUiLAogICAgICAgICAgICAgImZyIiwgImtrIiwgImRybCIsICJsZ2wiKQoKbGF5b3V0c19wbG90IDwtIG1hcChsYXlvdXRzLCBcKHgpIG15X2dncmFwaF9sYXlvdXRzKHgpKQoKe2xheW91dHNfcGxvdFtbMV1dIHwgbGF5b3V0c19wbG90W1syXV19IC8KICB7bGF5b3V0c19wbG90W1szXV0gfCBsYXlvdXRzX3Bsb3RbWzRdXX0gLyAKICB7bGF5b3V0c19wbG90W1s1XV0gfCBsYXlvdXRzX3Bsb3RbWzZdXX0gLwogIHtsYXlvdXRzX3Bsb3RbWzddXSB8IGxheW91dHNfcGxvdFtbOF1dfSAvCiAge2xheW91dHNfcGxvdFtbOV1dIHwgbGF5b3V0c19wbG90W1sxMF1dfSAvIAogIHtsYXlvdXRzX3Bsb3RbWzExXV0gfCBsYXlvdXRzX3Bsb3RbWzEyXV19IC8KICB7bGF5b3V0c19wbG90W1sxM11dIHwgbGF5b3V0c19wbG90W1sxNF1dfQpgYGAKCuOCqOODg+OCuOOBruihqOePvuOCguOBhOOCjeOBhOOCjemBuOOBueOBvuOBmeOAggoKYGBge3IgZmlnLmhlaWdodD0yMCwgZmlnLndpZHRoPTEwfQpnMyA8LSBtYXRyaXgoCiAgYygwLDEsMSwxLCAxLDAsMCwwLCAxLDEsMCwwLCAxLDEsMSwwKSwgbnJvdyA9IDQsIGJ5cm93ID0gVFJVRSwKICBkaW1uYW1lcyA9IGxpc3QoYygiQSIsICJCIiwgIkMiLCAiRCIpLGMoIkEiLCAiQiIsICJDIiwgIkQiKSkKKSB8PgogIGFzX3RibF9ncmFwaCgpCgpzZXQuc2VlZCgyKQpwMSA8LSBnMyB8PgogIGdncmFwaCgiZnIiKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemU9Niwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIpICsKICBnZW9tX2VkZ2VfbGluayhhcnJvdyA9IGFycm93KCksCiAgICAgICAgICAgICAgICAgZW5kX2NhcCA9IGNpcmNsZSgzLCAibW0iKSwKICAgICAgICAgICAgICAgICBzdGFydF9jYXAgPSBjaXJjbGUoMywgIm1tIikpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSkgKwogIGxhYnModGl0bGUgPSAiZ2VvbV9lZGdlX2xpbmsiKSArCiAgdGhlbWVfZ3JhcGgoKQoKc2V0LnNlZWQoMikKcDIgPC0gZzMgfD4KICBnZ3JhcGgoImZyIikgKwogIGdlb21fZWRnZV9hcmMoYXJyb3cgPSBhcnJvdygpLAogICAgICAgICAgICAgICAgZW5kX2NhcCA9IGNpcmNsZSgzLCAibW0iKSwKICAgICAgICAgICAgICAgIHN0YXJ0X2NhcCA9IGNpcmNsZSgzLCAibW0iKSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplPTYsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSkpICsKICBsYWJzKHRpdGxlID0gImdlb21fZWRnZV9hcmMiKSArCiAgdGhlbWVfZ3JhcGgoKQoKc2V0LnNlZWQoMikKcDMgPC0gZzMgfD4KICBnZ3JhcGgoImZyIikgKwogIGdlb21fZWRnZV9iZW5kKGFycm93ID0gYXJyb3coKSwKICAgICAgICAgICAgICAgICBlbmRfY2FwID0gY2lyY2xlKDMsICJtbSIpLAogICAgICAgICAgICAgICAgIHN0YXJ0X2NhcCA9IGNpcmNsZSgzLCAibW0iKSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplPTYsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSkpICsKICBsYWJzKHRpdGxlID0gImdlb21fZWRnZV9iZW5kIikgKwogIHRoZW1lX2dyYXBoKCkKCnNldC5zZWVkKDIpCnA0IDwtIGczIHw+CiAgZ2dyYXBoKCJmciIpICsKICBnZW9tX2VkZ2VfaGl2ZShhcnJvdyA9IGFycm93KCksCiAgICAgICAgICAgICAgICAgZW5kX2NhcCA9IGNpcmNsZSgzLCAibW0iKSwKICAgICAgICAgICAgICAgICBzdGFydF9jYXAgPSBjaXJjbGUoMywgIm1tIikpICsKICBnZW9tX25vZGVfcG9pbnQoc2l6ZT02LCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAib3JhbmdlIikgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpKSArCiAgbGFicyh0aXRsZSA9ICJnZW9tX2VkZ2VfaGl2ZSIpICsKICB0aGVtZV9ncmFwaCgpCgojIOikh+e3muWvvuW/nApzZXQuc2VlZCgyKQpwNSA8LSBnMyB8PgogIGdncmFwaCgiZnIiKSArCiAgZ2VvbV9lZGdlX2ZhbihhcnJvdyA9IGFycm93KCksCiAgICAgICAgICAgICAgICBlbmRfY2FwID0gY2lyY2xlKDMsICJtbSIpLAogICAgICAgICAgICAgICAgc3RhcnRfY2FwID0gY2lyY2xlKDMsICJtbSIpKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemU9Niwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSkgKwogIGxhYnModGl0bGUgPSAiZ2VvbV9lZGdlX2ZhbiIpICsKICB0aGVtZV9ncmFwaCgpCgpzZXQuc2VlZCgyKQpwNiA8LSBnMyB8PgogIGdncmFwaCgiZnIiKSArCiAgZ2VvbV9lZGdlX3BhcmFsbGVsKGFycm93ID0gYXJyb3coKSwKICAgICAgICAgICAgICAgICAgICAgZW5kX2NhcCA9IGNpcmNsZSgzLCAibW0iKSwKICAgICAgICAgICAgICAgICAgICAgc3RhcnRfY2FwID0gY2lyY2xlKDMsICJtbSIpKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemU9Niwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSkgKwogIGxhYnModGl0bGUgPSAiZ2VvbV9lZGdlX3BhcmFsbGVsIikgKwogIHRoZW1lX2dyYXBoKCkKCiMg6Ieq5bex44Or44O844OX6L+95YqgCnNldC5zZWVkKDIpCnA3IDwtIGRhdGEuZnJhbWUoCiAgZnJvbSA9IGMoIkEiLCAiQSIsICJBIiwgIkIiLCAiQyIsICJDIiwgIkQiLCAiRCIsICJEIiwgIkIiLCAiQyIsICJEIiksCiAgdG8gICA9IGMoIkIiLCAiQyIsICJEIiwgIkEiLCAiQSIsICJCIiwgIkEiLCAiQiIsICJDIiwgIkIiLCAiQyIsICJEIikKICApIHw+CiAgYXNfdGJsX2dyYXBoKCkgfD4KICBnZ3JhcGgoImZyIikgKwogIGdlb21fZWRnZV9mYW4oYXJyb3cgPSBhcnJvdygpLAogICAgICAgICAgICAgICAgZW5kX2NhcCA9IGNpcmNsZSgzLCAibW0iKSwKICAgICAgICAgICAgICAgIHN0YXJ0X2NhcCA9IGNpcmNsZSgzLCAibW0iKSkgKwogIGdlb21fZWRnZV9sb29wKGFycm93ID0gYXJyb3coKSwKICAgICAgICAgICAgICAgICBlbmRfY2FwID0gY2lyY2xlKDMsICJtbSIpLAogICAgICAgICAgICAgICAgIHN0YXJ0X2NhcCA9IGNpcmNsZSgzLCAibW0iKSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplPTYsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSkpICsKICBsYWJzKHRpdGxlID0gImdlb21fZWRnZV9sb29wIikgKwogIHRoZW1lX2dyYXBoKCkKCntwMSB8IHAyIH0gLwogIHtwMyB8IHA0IH0gLwogIHtwNSB8IHA2fSAvCiAge3A3IHwgcGxvdF9zcGFjZXIoKX0KYGBgCgojIyDjg43jg4Pjg4jjg6/jg7zjgq/jga7kuK3lv4PmgKfmjIfmqJkKCuOBleOBpuOAgeOBneOCjeOBneOCjeWIhuaekOOBo+OBveOBhOOBk+OBqOOCkuOBl+OBvuOBl+OCh+OBhuOBi+OAggoK44ON44OD44OI44Ov44O844Kv5qeL6YCg44GL44KJ5ZCE44OO44O844OJ44GM44Gp44KM44GP44KJ44GE5Lit5b+D55qE44Gq5b255Ymy44KS5p6c44Gf44GX44Gm44GE44KL44Gu44GL44KS5a6a6YeP5YyW44GZ44KL44Gu44GM44CM5Lit5b+D5oCn5oyH5qiZ44CN44Gn44GZ44CCCgpTTlPjgafoqIDjgYjjgbDjg5Xjgqnjg63jg6/jg7zjgYzlpJrjgYTjgbvjganph43opoHkurrnianjgaPjgb3jgYTjgajogIPjgYjjgonjgozjgb7jgZnjgojjga3jgILjgZ3jga7jgojjgYbjgarogIPjgYjmlrnjgafjgZnjgIIKCuS4reW/g+aAp+aMh+aomeOBq+OBr+OBhOOBj+OBpOOBi+OBruaJi+azleOBjOaPkOahiOOBleOCjOOBpuOBhOOBvuOBmeOAguOBk+OBk+OBp+OBr+S7o+ihqOeahOOBquOCguOBruOCkjPjgaTmjJnjgZLjgb7jgZnjgIIKCiAgIC0g5qyh5pWw5Lit5b+D5oCn77ya5o6l57aa44GX44Gm44GE44KL44Ko44OD44K444Gu5pWw44CC5pyJ5ZCR44Kw44Op44OV44Gu5aC05ZCI44Gv5LuW44Gu44OO44O844OJ44GL44KJ5YWl44Gj44Gm44GP44KL5YWl5qyh5pWw44CB5LuW44Gu44OO44O844OJ44G45Ye644Gm6KGM44GP5Ye65qyh5pWw44GM5Yy65Yil44GV44KM44G+44GZ44CCCiAgIC0g44Oa44O844K444Op44Oz44Kv77ya5qyh5pWw5Lit5b+D5oCn44Gv44GZ44G544Gm44Gu44Ko44OD44K444KS562J44GX44GP5omx44GE44G+44GZ44GM44CB5o6l57aa5YWI44Gu44OO44O844OJ44Gu5Lit5b+D5oCn44Gr5b+c44GY44Gm44Ko44OD44K444Gr6YeN44G/44Gl44GR44KS44GZ44KL44Gu44GM44Oa44O844K444Op44Oz44Kv44Gn44GZ44CCCiAgIC0g5aqS5LuL5Lit5b+D5oCn77ya5qyh5pWw44Gn44Gv44Gq44GP44CB57WM6Lev44Gr5rOo55uu44GZ44KL44Gu44GM5aqS5LuL5Lit5b+D5oCn44Gn44GZ44CC44GZ44G544Gm44Gu44OO44O844OJ6ZaT44Gu57WM6Lev44KS6ICD44GI44Gf44Go44GN44Gr44CB44OO44O844OJ44GU44Go44Gr6YCa6YGO44GZ44KL6aC75bqm44KS5pWw44GI44Gf44Gu44GM5aqS5LuL5Lit5b+D5oCn44Gn44GZ44CCCiAgIArlrp/pmpvjgavoqIjnrpfjgZfjgabjgb/jgb7jgZfjgofjgYbjgIIKCuODjeODg+ODiOODr+ODvOOCr+OBruOCquODluOCuOOCp+OCr+ODiCh0YmxfZ3JhcGjjgq/jg6njgrkp44Gr44Gv44OO44O844OJ44Go44Ko44OD44K444GuMuOBpOOBruaDheWgseOBjOagvOe0jeOBleOCjOOBpuOBhOOCi+OBruOBp+OAgeOBvuOBmmFjdGl2YXRlKCnplqLmlbDjgafjg47jg7zjg4njga7mlrnjgpLmjIflrprjgZfjgabjgYvjgonjgIHpgJrluLjjga7jg4fjg7zjgr/jg5Xjg6zjg7zjg6DjgajlkIzjgZjjgojjgYbjgattdXRhdGUoKeOBp+WQhOODjuODvOODieOBruS4reW/g+aAp+aMh+aomeOCkui/veWKoOOBl+OBvuOBmeOAggoKYGBge3IgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTEwfQpnYyA8LSBnMyB8PgogIGFjdGl2YXRlKG5vZGVzKSB8PgogIG11dGF0ZSgKICAgIGNkX2luID0gY2VudHJhbGl0eV9kZWdyZWUod2VpZ2h0cyA9IHdlaWdodCwgbW9kZSA9ICJpbiIpLAogICAgY2Rfb3V0ID0gY2VudHJhbGl0eV9kZWdyZWUod2VpZ2h0cyA9IHdlaWdodCwgbW9kZSA9ICJvdXQiKSwKICAgIGNkX2FsbCA9IGNlbnRyYWxpdHlfZGVncmVlKHdlaWdodHMgPSB3ZWlnaHQsIG1vZGUgPSAiYWxsIiksCiAgICBiZXR3ZWVuID0gY2VudHJhbGl0eV9iZXR3ZWVubmVzcyh3ZWlnaHRzID0gd2VpZ2h0KSwKICAgIHBhZ2VyYW5rID0gY2VudHJhbGl0eV9wYWdlcmFuayh3ZWlnaHRzID0gd2VpZ2h0KQogICkKCiMg5o+P55S7CnNldC5zZWVkKDEpCnAxIDwtIGdjIHw+CiAgZ2dyYXBoKCJmciIsIHdlaWdodHMgPSB3ZWlnaHQpICsKICBnZW9tX2VkZ2VfZmFuKAogICAgYWVzKHdpZHRoID0gd2VpZ2h0KSwKICAgIGNvbG9yID0gImdyYXk0MCIsIGFscGhhID0gMC41LGFycm93ID0gYXJyb3coKSwgZW5kX2NhcCA9IGNpcmNsZSg2LCAibW0iKSwgc3RhcnRfY2FwID0gY2lyY2xlKDYsICJtbSIpKSArCiAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMSw1KSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZT1jZF9pbiksIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMywxMikpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBjZF9pbikpICsKICBsYWJzKHRpdGxlID0gIuasoeaVsOS4reW/g+aAp++8iGlu77yJIikgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgYXNwZWN0LnJhdGlvID0gMSkKc2V0LnNlZWQoMSkKcDIgPC0gZ2MgfD4KICBnZ3JhcGgoImZyIiwgd2VpZ2h0cyA9IHdlaWdodCkgKwogIGdlb21fZWRnZV9mYW4oCiAgICBhZXMod2lkdGggPSB3ZWlnaHQpLAogICAgY29sb3IgPSAiZ3JheTQwIiwgYWxwaGEgPSAwLjUsYXJyb3cgPSBhcnJvdygpLCBlbmRfY2FwID0gY2lyY2xlKDYsICJtbSIpLCBzdGFydF9jYXAgPSBjaXJjbGUoNiwgIm1tIikpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygxLDUpKSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplPWNkX291dCksIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMywxNSkpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBjZF9vdXQpKSArCiAgbGFicyh0aXRsZSA9ICLmrKHmlbDkuK3lv4PmgKfvvIhvdXTvvIkiKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBhc3BlY3QucmF0aW8gPSAxKQpzZXQuc2VlZCgxKQpwMyA8LSBnYyB8PgogIGdncmFwaCgiZnIiLCB3ZWlnaHRzID0gd2VpZ2h0KSArCiAgZ2VvbV9lZGdlX2ZhbigKICAgIGFlcyh3aWR0aCA9IHdlaWdodCksCiAgICBjb2xvciA9ICJncmF5NDAiLCBhbHBoYSA9IDAuNSxhcnJvdyA9IGFycm93KCksIGVuZF9jYXAgPSBjaXJjbGUoNiwgIm1tIiksIHN0YXJ0X2NhcCA9IGNpcmNsZSg2LCAibW0iKSkgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDEsNSkpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemU9Y2RfYWxsKSwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYygzLDE1KSkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IGNkX2FsbCkpICsKICBsYWJzKHRpdGxlID0gIuasoeaVsOS4reW/g+aAp++8iGFsbO+8iSIpICsKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGFzcGVjdC5yYXRpbyA9IDEpCnNldC5zZWVkKDEpCnA0IDwtIGdjIHw+CiAgZ2dyYXBoKCJmciIsIHdlaWdodHMgPSB3ZWlnaHQpICsKICBnZW9tX2VkZ2VfZmFuKAogICAgYWVzKHdpZHRoID0gd2VpZ2h0KSwKICAgIGNvbG9yID0gImdyYXk0MCIsIGFscGhhID0gMC41LGFycm93ID0gYXJyb3coKSwgZW5kX2NhcCA9IGNpcmNsZSg2LCAibW0iKSwgc3RhcnRfY2FwID0gY2lyY2xlKDYsICJtbSIpKSArCiAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMSw1KSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZT1iZXR3ZWVuKSwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIm9yYW5nZSIpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYygzLDE1KSkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IGJldHdlZW4pKSArCiAgbGFicyh0aXRsZSA9ICLlqpLku4vkuK3lv4PmgKciKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBhc3BlY3QucmF0aW8gPSAxKQpzZXQuc2VlZCgxKQpwNSA8LSBnYyB8PgogIGdncmFwaCgiZnIiLCB3ZWlnaHRzID0gd2VpZ2h0KSArCiAgZ2VvbV9lZGdlX2ZhbigKICAgIGFlcyh3aWR0aCA9IHdlaWdodCksCiAgICBjb2xvciA9ICJncmF5NDAiLCBhbHBoYSA9IDAuNSxhcnJvdyA9IGFycm93KCksIGVuZF9jYXAgPSBjaXJjbGUoNiwgIm1tIiksIHN0YXJ0X2NhcCA9IGNpcmNsZSg2LCAibW0iKSkgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDEsNSkpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemU9cGFnZXJhbmspLCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAib3JhbmdlIikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsMTUpKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gcm91bmQocGFnZXJhbmssMikpKSArCiAgbGFicyh0aXRsZSA9ICLjg5rjg7zjgrjjg6njg7Pjgq8iKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBhc3BlY3QucmF0aW8gPSAxKQoKcDEgKyBwMiArIHAzICsgcDQgKyBwNSArIHBsb3Rfc3BhY2VyKCkgKyBwbG90X2xheW91dChuY29sID0gMikKYGBgCgrmlbDlgKTjgoLnorroqo3jgZfjgb7jgZfjgofjgYbjgIIKCuOBk+OBoeOCieOCgmFjdGl2YXRlKCnjgafjg47jg7zjg4njgpLmjIflrprjgZfjgabjgYvjgonjg4fjg7zjgr/jg5Xjg6zjg7zjg6DjgavlpInmj5vjgZnjgozjgbBPS+OBp+OBmeOAggoKYGBge3J9CmdjIHw+CiAgYWN0aXZhdGUobm9kZXMpIHw+CiAgYXNfdGliYmxlKCkKYGBgCgojIyDjgrPjg5/jg6Xjg4vjg4bjgqPmir3lh7oKCuasoeOBq+OCs+ODn+ODpeODi+ODhuOCo+aKveWHuuOCkuihjOOBhOOBvuOBmeOAguOBk+OCjOOBr+OCr+ODqeOCueOCv+ODvOWIhuaekOOBruODjeODg+ODiOODr+ODvOOCr+eJiOOBp+OBmeOAggoK44G+44Ga44Gv5YiG44GL44KK44KE44GZ44GE44ON44OD44OI44Ov44O844Kv44Gn6Kmm44GX44Gm44G/44G+44GX44KH44GG44CCCgrmmI7jgonjgYvjgas044Gk44Gu44Kw44Or44O844OX44GM5a2Y5Zyo44GX44G+44GZ44CCCgpgYGB7cn0KaXNsYW5kcyA8LSBwbGF5X2lzbGFuZHMoCiAgbl9pc2xhbmRzID0gNCwgCiAgc2l6ZV9pc2xhbmRzID0gMTUsCiAgcF93aXRoaW4gPSAwLjcsCiAgbV9iZXR3ZWVuID0gMwogICkKCmlzbGFuZHMgfD4KICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9mYW4oY29sb3IgPSAiZ3JheSIsIGFscGhhID0gMC43KSArCiAgZ2VvbV9ub2RlX3BvaW50KHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJvcmFuZ2UiLCBzaXplID0gNikgKwogIHRoZW1lX2dyYXBoKCkKYGBgCgrjgZPjga7jgrDjg6njg5XjgpLjgrPjg5/jg6Xjg4vjg4bjgqPmir3lh7rjgZfjgb7jgZnjgIIKCuOBk+OCjOOCguOBhOOBj+OBpOOBi+OBruaJi+azleOBjOaPkOahiOOBleOCjOOBpuOBiuOCiuOAgeS7iuWbnuOBr+S7peS4i+OBrjTjgaTjga7miYvms5XjgpLoqabjgZfjgabjgb/jgb7jgZnjgIIKCiAgIC0g5pyA6YGp5YyW6LKq5qyy44Ki44Or44K044Oq44K644OgCiAgIC0g44Or44O844O044Kn44Oz5rOVCiAgIC0g44Kk44Oz44OV44Kp44Oe44OD44OX5rOVCiAgIC0g44Ko44OD44K444Gu5aqS5LuL5Lit5b+D5oCn44Gr5Z+644Gl44GP44Kz44Of44Ol44OL44OG44Kj5oq95Ye6CgpgYGB7cn0KaXNsYW5kc19jbHVzdCA8LSBpc2xhbmRzIHw+CiAgYWN0aXZhdGUobm9kZXMpIHw+CiAgbXV0YXRlKAogICAgZmFzdF9ncmVlZHkgPSBhcy5mYWN0b3IoZ3JvdXBfZmFzdF9ncmVlZHkoKSksCiAgICBsb3V2YWluID0gYXMuZmFjdG9yKGdyb3VwX2xvdXZhaW4oKSksCiAgICBpbmZvbWFwID0gYXMuZmFjdG9yKGdyb3VwX2luZm9tYXAoKSksCiAgICBlZGdlX2JldHdlZW5uZXNzID0gYXMuZmFjdG9yKGdyb3VwX2VkZ2VfYmV0d2Vlbm5lc3MoKSkKICApCmBgYAoKCuODjeODg+ODiOODr+ODvOOCr+Wbs+OBp+eiuuiqjeOBl+OBvuOBl+OCh+OBhuOAguOBqeOBruaJi+azleOBp+OCgumBqeWIh+OBq+OCsOODq+ODvOODl+WIhuOBkeOBp+OBjeOBpuOBhOOCi+OCiOOBhuOBp+OBmeOAggoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpzZXQuc2VlZCgxKQpwMSA8LSBpc2xhbmRzX2NsdXN0IHw+CiAgZ2dyYXBoKCJmciIpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICJncmF5NDAiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoZmlsbCA9IGZhc3RfZ3JlZWR5KSwgc2l6ZSA9IDYsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnModGl0bGUgPSAiR3JlZWR5IG9wdGltaXphdGlvbiBvZiBtb2R1bGFyaXR5IikgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnNldC5zZWVkKDEpCnAyIDwtIGlzbGFuZHNfY2x1c3QgIHw+CiAgZ2dyYXBoKCJmciIpICsKICBnZW9tX2VkZ2VfZmFuKGNvbG9yID0gImdyYXk0MCIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhmaWxsID0gbG91dmFpbiksIHNpemUgPSA2LCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIlRoZSBMb3V2YWluIGFsZ29yaXRobSIpICsKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpzZXQuc2VlZCgxKQpwMyA8LSBpc2xhbmRzX2NsdXN0ICB8PgogIGdncmFwaCgiZnIiKSArCiAgZ2VvbV9lZGdlX2Zhbihjb2xvciA9ICJncmF5NDAiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoZmlsbCA9IGluZm9tYXApLCBzaXplID0gNiwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siKSArCiAgbGFicyh0aXRsZSA9ICJUaGUgSW5mb21hcCBhbGdvcml0aG0iKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKc2V0LnNlZWQoMSkKcDQgPC0gaXNsYW5kc19jbHVzdCAgfD4KICBnZ3JhcGgoImZyIikgKwogIGdlb21fZWRnZV9mYW4oY29sb3IgPSAiZ3JheTQwIiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGZpbGwgPSBlZGdlX2JldHdlZW5uZXNzKSwgc2l6ZSA9IDYsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnModGl0bGUgPSAiQ29tbXVuaXR5IHN0cnVjdHVyZSBkZXRlY3Rpb25cbiBiYXNlZCBvbiBlZGdlIGJldHdlZW5uZXNzIikgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCntwMXxwMn0ve3AzfHA0fQpgYGAKCuOBquOBiuOAgeacgOmBqeWMluiyquassuOCouODq+OCtOODquOCuuODoOOBqOOCqOODg+OCuOOBruWqkuS7i+S4reW/g+aAp+OBq+WfuuOBpeOBj+OCs+ODn+ODpeODi+ODhuOCo+aKveWHuuOBr+majuWxpOWei+OBruaJi+azleOBquOBruOBp+OAgeODh+ODs+ODieODreOCsOODqeODoOOCkuaPj+OBj+OBk+OBqOOCguOBp+OBjeOBvuOBmeOAggoKYGBge3J9CmlzbGFuZHMgfD4KICBjbHVzdGVyX2Zhc3RfZ3JlZWR5KCkgfD4KICBwbG90X2RlbmRyb2dyYW0obWFpbiA9ICLmnIDpganljJbosqrmrLLjgqLjg6vjgrTjg6rjgrrjg6AiKQpgYGAKCuepuuaJi+OCr+ODqeODluOBruODh+ODvOOCv+OBp+OCguippuOBl+OBpuOBv+OBvuOBl+OCh+OBhuOAggoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQprYXJhdGVfY2x1c3QgPC0ga2FyYXRlIHw+CiAgYWN0aXZhdGUobm9kZXMpIHw+CiAgbXV0YXRlKAogICAgZmFzdF9ncmVlZHkgPSBhcy5mYWN0b3IoZ3JvdXBfZmFzdF9ncmVlZHkoKSksCiAgICBsb3V2YWluID0gYXMuZmFjdG9yKGdyb3VwX2xvdXZhaW4oKSksCiAgICBpbmZvbWFwID0gYXMuZmFjdG9yKGdyb3VwX2luZm9tYXAoKSksCiAgICBlZGdlX2JldHdlZW5uZXNzID0gYXMuZmFjdG9yKGdyb3VwX2VkZ2VfYmV0d2Vlbm5lc3MoKSkKICApCnNldC5zZWVkKDEpCnAxIDwtIGthcmF0ZV9jbHVzdCB8PgogIGdncmFwaCgiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoY29sb3IgPSAiZ3JheTQwIiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGZpbGwgPSBmYXN0X2dyZWVkeSksIHNpemUgPSA2LCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIkdyZWVkeSBvcHRpbWl6YXRpb24gb2YgbW9kdWxhcml0eSIpICsKICB0aGVtZV9ncmFwaCgpCnNldC5zZWVkKDEpCnAyIDwtIGthcmF0ZV9jbHVzdCAgfD4KICBnZ3JhcGgoImZyIikgKwogIGdlb21fZWRnZV9mYW4oY29sb3IgPSAiZ3JheTQwIiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGZpbGwgPSBsb3V2YWluKSwgc2l6ZSA9IDYsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnModGl0bGUgPSAiVGhlIExvdXZhaW4gYWxnb3JpdGhtIikgKwogIHRoZW1lX2dyYXBoKCkKc2V0LnNlZWQoMSkKcDMgPC0ga2FyYXRlX2NsdXN0ICB8PgogIGdncmFwaCgiZnIiKSArCiAgZ2VvbV9lZGdlX2Zhbihjb2xvciA9ICJncmF5NDAiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoZmlsbCA9IGluZm9tYXApLCBzaXplID0gNiwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siKSArCiAgbGFicyh0aXRsZSA9ICJUaGUgSW5mb21hcCBhbGdvcml0aG0iKSArCiAgdGhlbWVfZ3JhcGgoKQpzZXQuc2VlZCgxKQpwNCA8LSBrYXJhdGVfY2x1c3QgIHw+CiAgZ2dyYXBoKCJmciIpICsKICBnZW9tX2VkZ2VfZmFuKGNvbG9yID0gImdyYXk0MCIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhmaWxsID0gZWRnZV9iZXR3ZWVubmVzcyksIHNpemUgPSA2LCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIkNvbW11bml0eSBzdHJ1Y3R1cmUgZGV0ZWN0aW9uXG4gYmFzZWQgb24gZWRnZSBiZXR3ZWVubmVzcyIpICsKICB0aGVtZV9ncmFwaCgpCgp7cDF8cDJ9L3twM3xwNH0KYGBgCgojIyDlj4LogIPmlofnjK4KCltIYW5kYm9vayBvZiBHcmFwaHMgYW5kIE5ldHdvcmtzIGluIFBlb3BsZSBBbmFseXRpY3MgV2l0aCBFeGFtcGxlcyBpbiBSIGFuZCBQeXRob25dKGh0dHBzOi8vb25hLWJvb2sub3JnL2dpdGJvb2svKQoKW+Wun+i3teOBp+WtpuOBtuODjeODg+ODiOODr+ODvOOCr+WIhuaekO+8iFRva3lvLlIjMzIsIDIwMTPvvIldKGh0dHBzOi8vd3d3LnNsaWRlc2hhcmUubmV0L01pdHN1bm9yaVNhdG8vdG9reW9yMzItbmV0d29yay1hbmFseXNpcy0yNDQ0MjUxNikKClt7dGlkeWdyYXBofeOBqHtnZ3JhcGh944Gr44KI44KLIOODouODgOODs+OBquODjeODg+ODiOODr+ODvOOCr+WIhuaekChUb2t5by5SICM2OSwgMjAxOCldKGh0dHBzOi8vd3d3LnNsaWRlc2hhcmUubmV0L2thc2hpdGFuL3RpZHlncmFwaGdncmFwaC12ZXItMTUyMzY4MzIyKQogClvjgI5S44Gn5a2m44G244OH44O844K/44K144Kk44Ko44Oz44K5OCDjg43jg4Pjg4jjg6/jg7zjgq/liIbmnpAg56ysMueJiOOAj10oaHR0cHM6Ly93d3cua3lvcml0c3UtcHViLmNvLmpwL2Jvb2svYjEwMDAzODQ1Lmh0bWwpCiAKCuS7peS4iuOAgg==